package is.glif.fenius.translators.glambda;

import net.glambda.schemas.gnswsi3._2008._12.rdl.ResourceRequirementsType;
import net.glambda.schemas.gnswsi3._2008._12.rdl.TimeframeType;
import net.glambda.schemas.gnswsi3._2008._12.rdl.ReservationStatusType;
import net.glambda.schemas.gnswsi3._2008._12.rdl.ReservationStatusHolderType;
import net.glambda.schemas.gnswsi3._2008._12.rdl.ReservationResourcesType;
import net.glambda.schemas.gnswsi3._2008._12.rdl.AvailableResourceQueryType;
import net.glambda.schemas.gnswsi3._2008._12.rdl.AvailableResourceQueryResultType;
import net.glambda.schemas.gnswsi3._2008._12.rdl.ResourceStatusType;
import net.glambda.schemas.gnswsi3._2008._12.ndl.NetworkResourcesType;

import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.ReservationFactoryPortType;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.GetReservationStatusRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.GetReservationStatusResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.GetResourcePropertyRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.GetResourcePropertyResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ReservationResourceProperties;
import net.glambda.schemas.gnswsi3._2008._12.reservation.ReservationPortType;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ModifyAllRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ModifyAllResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ModifyRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ModifyResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ReleaseAllRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ReleaseAllResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ReleaseRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ReleaseResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ReserveRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservation.types.ReserveResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservation.ReservationFaultMessage;
import net.glambda.schemas.gnswsi3._2008._12.reservation.service.ReservationService;

import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.types.CreateRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.types.CreateResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.types.GetAvailableResourcesRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.types.GetAvailableResourcesResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.types.CheckResourceAvailabilityRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.types.CheckResourceAvailabilityResponse;

import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.service.ReservationFactoryService;
import net.glambda.schemas.gnswsi3._2008._12.reservationfactory.ReservationFactoryFaultMessage;

import net.glambda.schemas.gnswsi3._2008._12.rdl.CommandStatusType;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.ReservationCommandPortType;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.types.CommitRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.types.CommitResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.types.AbortRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.types.AbortResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.types.GetCommandStatusRequest;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.types.GetCommandStatusResponse;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.ReservationCommandFaultMessage;
import net.glambda.schemas.gnswsi3._2008._12.reservationcommand.service.ReservationCommandService;

import javax.xml.namespace.QName;
import javax.xml.ws.EndpointReference;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;
import javax.xml.bind.JAXBElement;

import java.rmi.RemoteException;

import java.util.List;
import java.net.URL;
import java.net.MalformedURLException;

public class ResourceManagerClient
{
    long T_create_s = 0, T_create_e = 0, T_create_ss = 0, T_create_ee;
    long T_reserve_s = 0, T_reserve_e = 0;
    long T_status_s = 0, T_status_e = 0;
    long T_prepare_s = 0, T_prepare_e = 0;
    long T_commit_s = 0, T_commit_e = 0;
    long T_committed_s = 0, T_committed_e = 0;
    long T_status_prepare = 0, T_status_commit = 0;
    long T_reservation_resource_s = 0, T_reservation_resource_e = 0;
    long T_reservation_resource_cs = 0, T_reservation_resource_ce = 0;
    boolean isMeasureTime = true;
    boolean isMeasureTime2 = false;

    /* 60 sec wait for command status change */
    private static final int count = 60; 
    
    String factoryURI = null;
    URL url = null;
    String DN = null;
    String credential;
    Service service = null;
    ReservationFactoryService factory = null;

    EndpointReference endpoint = null;
    EndpointReference cmd = null;
    ReservationFactoryPortType factoryPort = null;

    public ResourceManagerClient
	(String location, String DN, String credential)
	throws ReservationFactoryFaultMessage, MalformedURLException
    {
	this.factoryURI = location;
	this.DN = DN;
	this.credential = credential;
	URL url = new URL(factoryURI);

	if (isMeasureTime2) T_create_s = System.nanoTime();
	op_create();
	if (isMeasureTime2) T_create_e = System.nanoTime();
    }

    private void secondTimer(int t) 
    {
	try {
	    Thread.sleep(t*1000);
	} catch (Exception ex) {}
    }

    private void waitCommandStatus
	(CommandStatusType current, CommandStatusType wait)
	throws RemoteException, ReservationCommandFaultMessage
    {
	int w = count;
	while (true) {
	    if (isMeasureTime2) T_status_s = System.nanoTime();
	    CommandStatusType c = op_getCommandStatus();
	    if (isMeasureTime2) T_status_e = System.nanoTime();

	    System.err.println("CommandStatus=" + c + ", waiting for " + wait);

	    if (c == null) {
		throw new RemoteException
		    ("CommandStatus Error::CommandStatus is null.");
	    } else if (c == current) {
		secondTimer(1);
		w --;
	    } else if (c == wait) {
		break;
	    } else {
		throw new RemoteException
		    ("CommandStatus Error::CommandStatus is " + c + 
		     ", not " + wait + ".");
	    }
	    
	    if (w == 0) {
		throw new RemoteException
		    ("CommandStatus Timeout::CommandStatus is " + c + 
		     ", not " + wait + ".");
	    }
	}
    }

    public void reserve_only(ResourceRequirementsType req) 
	throws RemoteException, ReservationFaultMessage, ReservationCommandFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return;
	}

	op_reserve(req);
	waitCommandStatus
	    (CommandStatusType.INITIAL, CommandStatusType.PREPARED);
    }

    public void reserve(ResourceRequirementsType req) 
	throws RemoteException, ReservationFaultMessage, ReservationCommandFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return;
	}

	if (isMeasureTime2) T_reserve_s = System.nanoTime();
	op_reserve(req);
	if (isMeasureTime2) T_prepare_s = T_reserve_e = System.nanoTime();

	waitCommandStatus
	    (CommandStatusType.INITIAL, CommandStatusType.PREPARED);

	if (isMeasureTime || isMeasureTime2) {
	    T_commit_s = T_prepare_e = System.nanoTime();
	    T_status_prepare = T_status_e - T_status_s;
	}
	op_commit();
	if (isMeasureTime || isMeasureTime2) 
	    T_committed_s = T_commit_e = System.nanoTime();

	waitCommandStatus
	    (CommandStatusType.PREPARED, CommandStatusType.COMMITTED);

	if (isMeasureTime || isMeasureTime2) {
	    T_committed_e = System.nanoTime();
	    T_status_commit = T_status_e - T_status_s;
	}
	op_destroy();
	if (isMeasureTime) T_create_ee = System.nanoTime();
    }

    public void modify(ResourceRequirementsType req)
	throws RemoteException, ReservationFaultMessage, ReservationCommandFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return;
	}

	op_modify(req);
	waitCommandStatus
	    (CommandStatusType.INITIAL, CommandStatusType.PREPARED);
	op_commit();
	waitCommandStatus
	    (CommandStatusType.PREPARED, CommandStatusType.COMMITTED);
	op_destroy();
    }

    public void modifyAll(TimeframeType req)
	throws RemoteException, ReservationFaultMessage, ReservationCommandFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return;
	}

	op_modifyAll(req);
	waitCommandStatus
	    (CommandStatusType.INITIAL, CommandStatusType.PREPARED);
	op_commit();
	waitCommandStatus
	    (CommandStatusType.PREPARED, CommandStatusType.COMMITTED);
	op_destroy();
    }

    public void release(String[] subkey)
	throws RemoteException, ReservationFaultMessage, ReservationCommandFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return;
	}

	op_release(subkey);
	waitCommandStatus
	    (CommandStatusType.INITIAL, CommandStatusType.PREPARED);
	op_commit();
	waitCommandStatus
	    (CommandStatusType.PREPARED, CommandStatusType.COMMITTED);
	op_destroy();
    }

    public void releaseAll()
	throws RemoteException, ReservationFaultMessage, ReservationCommandFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return;
	}

	op_releaseAll();
	waitCommandStatus
	    (CommandStatusType.INITIAL, CommandStatusType.PREPARED);
	op_commit();
	waitCommandStatus
	    (CommandStatusType.PREPARED, CommandStatusType.COMMITTED);
	op_destroy();
    }

    public void op_init()
	throws ReservationFactoryFaultMessage
    {
	service = Service.create(ReservationFactoryService.SERVICE);

	W3CEndpointReferenceBuilder builder = new W3CEndpointReferenceBuilder();
        builder.address(factoryURI);
        builder.serviceName(ReservationFactoryService.SERVICE);
        builder.wsdlDocumentLocation(factoryURI + "?wsdl");
        endpoint = builder.build();

	factoryPort = service.getPort(endpoint, ReservationFactoryPortType.class, 
				      new WebServiceFeature[0]);
    }
    
    public void op_create()
	throws ReservationFactoryFaultMessage
    {
	if (factoryPort == null) op_init();

	if (isMeasureTime) T_create_ss = System.nanoTime();
	CreateRequest request = new CreateRequest();
	if (isMeasureTime) T_create_s = System.nanoTime();
	request.setDistinguishedName(DN);
	CreateResponse response = factoryPort.create(request);
	if (isMeasureTime) T_create_e = System.nanoTime();
	endpoint = response.getReservationEPR();
    }
    
    public EndpointReference op_reserve(ResourceRequirementsType req) 
	throws ReservationFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return null;
	}

	service = Service.create(ReservationService.SERVICE);
        ReservationPortType port =
            service.getPort(endpoint, ReservationPortType.class, 
			    new WebServiceFeature[0]);

	ReserveRequest request = new ReserveRequest();
	request.setReservationEPR((W3CEndpointReference) endpoint);
	request.setResourceRequirements(req);

	if (isMeasureTime) T_reserve_s = System.nanoTime();
	ReserveResponse response = port.reserve(request);
	if (isMeasureTime) {
	    T_reserve_e = System.nanoTime();
	    T_prepare_s = T_reserve_e;
	}

	cmd = response.getReservationCommandEPR();
	return cmd;
    }

    public EndpointReference op_modify(ResourceRequirementsType req)
	throws ReservationFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return null;
	}

	service = Service.create(ReservationService.SERVICE);
        ReservationPortType port =
            service.getPort(endpoint, ReservationPortType.class, 
			    new WebServiceFeature[0]);

	ModifyRequest request = new ModifyRequest();
	request.setReservationEPR((W3CEndpointReference) endpoint);
	request.setResourceRequirements(req);
        ModifyResponse response = port.modify(request);
        cmd = response.getReservationCommandEPR();
	return cmd;
    }

    public EndpointReference op_modifyAll(TimeframeType req)
	throws ReservationFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return null;
	}

	service = Service.create(ReservationService.SERVICE);
        ReservationPortType port =
            service.getPort(endpoint, ReservationPortType.class, 
			    new WebServiceFeature[0]);

	ModifyAllRequest request = new ModifyAllRequest();
	request.setReservationEPR((W3CEndpointReference) endpoint);
	request.setTimeframe(req);
        ModifyAllResponse response = port.modifyAll(request);
        cmd = response.getReservationCommandEPR();
	return cmd;
    }

    public EndpointReference op_release(String[] subkey)
	throws ReservationFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return null;
	}
	
	service = Service.create(ReservationService.SERVICE);
        ReservationPortType port =
            service.getPort(endpoint, ReservationPortType.class, 
			    new WebServiceFeature[0]);

	ReleaseRequest request = new ReleaseRequest();
	request.setReservationEPR((W3CEndpointReference) endpoint);
	List<String> l = request.getReservationID();
	for (int i = 0; i < subkey.length; i++) {
	    l.add(subkey[i]);
	}
        ReleaseResponse response = port.release(request);
        cmd = response.getReservationCommandEPR();
	return cmd;
    }

    public EndpointReference op_releaseAll()
	throws ReservationFaultMessage
    {
	if (cmd != null) {
	    System.err.println
		("Command sequence Error, next operation is commit/abort.");
	    return null;
	}
	
	service = Service.create(ReservationService.SERVICE);
        ReservationPortType port =
            service.getPort(endpoint, ReservationPortType.class, 
			    new WebServiceFeature[0]);

	ReleaseAllRequest request = new ReleaseAllRequest();
	request.setReservationEPR((W3CEndpointReference) endpoint);
        ReleaseAllResponse response = port.releaseAll(request);
        cmd = response.getReservationCommandEPR();
	return cmd;
    }

    public void op_commit()
	throws ReservationCommandFaultMessage
    {
	if (cmd == null) {
	    System.err.println
		("Command sequence Error, next operation is reserve/release/modify.");
	    return;
	}

        service = Service.create(ReservationCommandService.SERVICE);
        ReservationCommandPortType port =
            service.getPort(cmd, ReservationCommandPortType.class, 
			    new WebServiceFeature[0]);
	
	CommitRequest request = new CommitRequest();
	request.setReservationCommandEPR((W3CEndpointReference) cmd);

	if (isMeasureTime) T_commit_s = System.nanoTime();
	CommitResponse response = port.commit(request);
	if (isMeasureTime) T_commit_e = System.nanoTime();
    }

    public void op_abort()
	throws ReservationCommandFaultMessage
    {
	if (cmd == null) {
	    System.err.println
		("Command sequence Error, next operation is reserve/release/modify.");
	    return;
	}

        service = Service.create(ReservationCommandService.SERVICE);
        ReservationCommandPortType port =
            service.getPort(cmd, ReservationCommandPortType.class, 
			    new WebServiceFeature[0]);

	AbortRequest request = new AbortRequest();
	request.setReservationCommandEPR((W3CEndpointReference) cmd);
	AbortResponse response = port.abort(request);
    }

    private void op_destroy()
	throws ReservationCommandFaultMessage
    {
	if (cmd == null) {
	    System.err.println
		("Command sequence Error, next operation is reserve/release/modify.");
	    return;
	}
	/*
        service = Service.create(ReservationCommandService.SERVICE);
        ReservationCommandPortType port =
            service.getPort(cmd, ReservationCommandPortType.class, 
			    new WebServiceFeature[0]);

	Destroy request = new Destroy();
	request.setReservationCommandEPR((W3CEndpointReference) cmd);
	DestroyResponse response = port.destroy(request);
	*/
	cmd = null;
    }

    public ReservationResourcesType[] getReservationResource()
	throws ReservationFaultMessage
    {
        service = Service.create(ReservationService.SERVICE);
        ReservationPortType port =
            service.getPort(endpoint, ReservationPortType.class,
			    new WebServiceFeature[0]);

	GetResourcePropertyRequest request = new GetResourcePropertyRequest();
	request.setReservationEPR((W3CEndpointReference) endpoint);
	QName name = new QName("http://schemas.glambda.net/gnswsi3/2008/12/rdl", 
			       "ReservationResources");
	request.setPropertyName(name);

        GetResourcePropertyResponse response = port.getResourceProperty(request);
	List<Object> l =  response.getAny();

	int size = l.size();
	ReservationResourcesType[] rrt = new ReservationResourcesType[size];
	for (int i = 0; i < size; i++) {
	    JAXBElement o = (JAXBElement) l.get(i);
	    Object oo =  o.getValue();
	    if (oo instanceof ReservationResourcesType) {
		rrt[i] = (ReservationResourcesType) oo;
	    }
	}
	return rrt;
    }

    public ReservationStatusType[] getReservationStatus(String[] subkey)
	throws ReservationFaultMessage
    {
	service = Service.create(ReservationService.SERVICE);
        ReservationPortType port =
            service.getPort(endpoint, ReservationPortType.class, 
			    new WebServiceFeature[0]);

        GetReservationStatusRequest request = new GetReservationStatusRequest();
        request.setReservationEPR((W3CEndpointReference) endpoint);
	List<String> l = request.getReservationID();
	if (subkey != null && subkey.length > 0) {
	    for(int i = 0; i < subkey.length; i++) {
		l.add(subkey[i]);
	    }
	}
	    
        GetReservationStatusResponse response = port.getReservationStatus(request);
	
	List<ReservationStatusHolderType> holder = response.getReservationStatusHolder();
	int size = holder.size();
	ReservationStatusType[] result = new ReservationStatusType[size];
	for (int i = 0; i < size; i++) {
	    String key = holder.get(i).getReservationID();
	    result[i] = holder.get(i).getReservationStatus();
	    
	    if (subkey[i] != null && subkey.equals(key)) {
		System.err.println("****** BAD order, check key ******");
	    }
	}
	return result;
    }

    public CommandStatusType op_getCommandStatus()
	throws ReservationCommandFaultMessage
    {
	if (cmd == null) {
	    System.err.println
	    ("Command sequence Error, Command Endpoint Reference == null.");
	    return null;
	}

        service = Service.create(ReservationCommandService.SERVICE);
        ReservationCommandPortType port =
            service.getPort(cmd, ReservationCommandPortType.class, 
			    new WebServiceFeature[0]);

	GetCommandStatusRequest request = new GetCommandStatusRequest();
	request.setReservationCommandEPR((W3CEndpointReference) cmd);
	GetCommandStatusResponse response = port.getCommandStatus(request);

	if (response == null) {
	    // System.err.println("null return.");
	    return null;
	}
	CommandStatusType status = response.getCommandStatus();
	return status;
    }

    public AvailableResourceQueryResultType[] getAvailableResources
	(String DN, String credentialString, AvailableResourceQueryType[] query)
	throws ReservationFactoryFaultMessage
    {
	if (query == null) return null;
	if (factoryPort == null) op_init();

	GetAvailableResourcesRequest request = new GetAvailableResourcesRequest();
	request.setDistinguishedName(DN);
	List<AvailableResourceQueryType> reql = request.getAvailableResourceQuery();
	for (int i = 0; i < query.length; i ++) {
	    reql.add(query[i]);
	}

	GetAvailableResourcesResponse response = 
	    factoryPort.getAvailableResources(request);

	List<AvailableResourceQueryResultType> resl =
	    response.getAvailableResourceQueryResult();
	AvailableResourceQueryResultType[] result = 
	    new AvailableResourceQueryResultType[resl.size()];
	for (int i = 0; i < resl.size(); i ++) {
	    result[i] = resl.get(i);
	}
	return result;
    }
    
    public ResourceStatusType[] checkResourceAvailability
	(String DN, String credentialString, ReservationResourcesType[] query)
	throws ReservationFactoryFaultMessage
    {
	if (query == null) return null;
	if (factoryPort == null) op_init();

	CheckResourceAvailabilityRequest request = new CheckResourceAvailabilityRequest();
	request.setDistinguishedName(DN);
	List<ReservationResourcesType> reql = request.getReservationResources();
	for (int i = 0; i < query.length; i ++) {
	    reql.add(query[i]);
	}

	CheckResourceAvailabilityResponse response = 
	    factoryPort.checkResourceAvailability(request);

	List<ResourceStatusType> resl = response.getResourceStatus();
	ResourceStatusType[] result = new ResourceStatusType[resl.size()];
	for (int i = 0; i < resl.size(); i ++) {
	    result[i] = resl.get(i);
	}
	return result;
    }

    protected void finalize()
    {
	if (endpoint == null) return;
	/*
	try {
	    service = Service.create(ReservationService.SERVICE);
	    ReservationPortType port =
		service.getPort(endpoint, ReservationPortType.class, 
				new WebServiceFeature[0]);

	    Destroy request = new Destroy();
	    request.setReservationEPR((W3CEndpointReference) endpoint);
	    DestroyResponse response = port.destroy(request);
	} catch (Exception ex) {}
	// System.err.println("destroy");
	*/
	endpoint = null;
    }

    public void printResultMeasurement()
    {
	long T_create = (T_create_e - T_create_s) / 1000; // ms
	long T_reserve = (T_reserve_e - T_reserve_s) / 1000;
	long T_prepare = (T_prepare_e - T_prepare_s) / 1000;
	long T_commit = (T_commit_e - T_commit_s) / 1000;
	long T_committed = (T_committed_e - T_committed_s) / 1000;
	T_status_prepare /= 1000;
	T_status_commit /= 1000;
	long T_total = (T_create_ee - T_create_ss) / 1000;
	long T_reservation_resource = (T_reservation_resource_e - T_reservation_resource_s) / 1000 ;
	long T_reservation_resource_c = (T_reservation_resource_ce - T_reservation_resource_cs) / 1000;

	System.err.println("\t" + T_create + "\t" + T_reserve + "\t" + T_prepare +
			   "\t" + T_status_prepare + "\t" + T_commit + "\t"  +
			   T_committed + "\t" + T_status_commit + "\t" + T_total +
			   "\t" + T_reservation_resource_c +
			   "\t" + T_reservation_resource);
    }
}
